
import { getLengthFor } from './data-utils';


/**
 *  converts the PCM data of the wav file (each sample stored as 8 or 16 or 24 bit value) into
 *  a format expected by the libflac-encoder method (each sample stored as 32 bit value in a 32-bit array)
 */
export function wav_file_processing_convert_to32bitdata(arraybuffer: ArrayBuffer, bps: number): Int32Array | undefined {

	var decFunc;
	switch(bps){
		case 8:
			decFunc = convert_8bitdata_to32bitdata;
			break;
		case 16:
			decFunc = convert_16bitdata_to32bitdata;
			break;
		case 24:
			decFunc = convert_24bitdata_to32bitdata;
			break;
	}
	if(!decFunc){
		// -> unsupported bit-depth
		return void(0);
	}

	var bytes = bps / 8;
	var ab_i16 = new DataView(arraybuffer, 44);
	var buf_length = ab_i16.byteLength;
	var buf32_length = buf_length / bytes;
	var buffer_i32 = new Int32Array(buf32_length);
	var view = new DataView(buffer_i32.buffer);
	var index = 0;
	for (var j = 0; j < buf_length; j+=bytes){
		view.setInt32(index, decFunc(ab_i16,j), true);
		index += 4;
	}
	return buffer_i32;
}

/**
 *  converts the PCM data of the wav file (each sample stored as 8 bit value) into
 *  a format expected by the libflac-encoder method (each sample stored as 32 bit value in a 32-bit array)
 */
function convert_8bitdata_to32bitdata(dataView: DataView, i: number) : number {
	return dataView.getUint8(i) - 128 /* 0x80 */;
}

/**
 *  converts the PCM data of the wav file (each sample stored as 16 bit value) into
 *  a format expected by the libflac-encoder method (each sample stored as 32 bit value in a 32-bit array)
 */
function convert_16bitdata_to32bitdata(dataView: DataView, i: number) : number {
	return dataView.getInt16(i, true);
}

/**
 *  converts the PCM data of the wav file (each sample stored as 24 bit value) into
 *  a format expected by the libflac-encoder method (each sample stored as 32 bit value in a 32-bit array)
 */
function convert_24bitdata_to32bitdata(dataView: DataView, i: number) : number {
	var b = (((dataView.getUint8(i + 2) << 8) | dataView.getUint8(i + 1)) << 8) | dataView.getUint8(i);
	if ((b & 8388608 /* 0x00800000 */ ) > 0) {
		b |= 4278190080;// 0xFF000000;
	} else {
		b &= 16777215;// 0x00FFFFFF;
	}
	return b;
}

export function interleave(recBuffers: Uint8Array[][], channels: number, bitsPerSample: number): Uint8Array {

	let byteLen = bitsPerSample / 8;

	//NOTE 24-bit samples are padded with 1 byte
	const pad8 = (bitsPerSample === 24 || bitsPerSample === 8)? 1 : 0;
	if(pad8){
		byteLen += pad8;
	}

	//calculate total length for interleaved data
	let dataLength = 0;
	for(let i=0; i < channels; ++i){
		dataLength += getLengthFor(recBuffers, i, byteLen, pad8);
	}

	const result = new Uint8Array(dataLength);

	let buff: Uint8Array[],
		buffLen = 0,
		index = 0,
		inputIndex = 0,
		ch_i = 0,
		b_i = 0,
		pad_i: number | boolean = false,
		ord = false;

	for(let arrNum = 0, arrCount = recBuffers.length; arrNum < arrCount; ++arrNum){

		//for each buffer (i.e. array of Uint8Arrays):
		buff = recBuffers[arrNum];
		buffLen = buff[0].length;
		inputIndex = 0;
		pad_i = false;
		ord = false;

		//interate over buffer
		while(inputIndex < buffLen){

			//write channel data
			for(ch_i=0; ch_i < channels; ++ch_i){
				//write sample-length
				for(b_i=0; b_i < byteLen; ++b_i){
					// write data & update target-index
					if(pad8) {
						pad_i = pad8 && (b_i === byteLen - pad8);
						if(pad_i){
							if(buff[ch_i][inputIndex + b_i] !== 0 && buff[ch_i][inputIndex + b_i] !== 255){
								console.error('[ERROR] mis-aligned padding: ignoring non-padding value (padding should be 0 or 255) at '+(inputIndex + b_i)+' -> ', buff[ch_i][inputIndex + b_i]);
							}
						} else {
							if(bitsPerSample === 8){
								ord = buff[ch_i][inputIndex + b_i + 1] === 0;
								result[index++] = ord? buff[ch_i][inputIndex + b_i] | 128 : buff[ch_i][inputIndex + b_i] & 127;
							} else {
								result[index++] = buff[ch_i][inputIndex + b_i];
							}

						}
					} else {
						result[index++] = buff[ch_i][inputIndex + b_i];
					}
				}
			}
			//update source-index
			inputIndex+=byteLen;
		}
	}
	return result;
}

/**
 * write PCM data to a WAV file, incl. header
 *
 * @param samples the PCM audio data
 * @param sampleRate the sample rate for the audio data
 * @param channels the number of channels that the audio data contains
 * @param bitsPerSample the bit-per-sample
 *
 * @returns the WAV data incl. header
 */
export function encodeWAV(samples: Uint8Array, sampleRate: number, channels: number, bitsPerSample: number): DataView {

	var bytePerSample = bitsPerSample / 8;
	var length = samples.length * samples.BYTES_PER_ELEMENT;

	var buffer = new ArrayBuffer(44 + length);
	var view = new DataView(buffer);

	/* RIFF identifier */
	writeString(view, 0, 'RIFF');
	/* file length */
	view.setUint32(4, 36 + length, true);
	/* RIFF type */
	writeString(view, 8, 'WAVE');
	/* format chunk identifier */
	writeString(view, 12, 'fmt ');
	/* format chunk length */
	view.setUint32(16, 16, true);
	/* sample format (raw) */
	view.setUint16(20, 1, true);
	/* channel count */
	view.setUint16(22, channels, true);
	/* sample rate */
	view.setUint32(24, sampleRate, true);
	/* byte rate (sample rate * block align) */
	view.setUint32(28, sampleRate * channels * bytePerSample, true);
	/* block align (channel count * bytes per sample) */
	view.setUint16(32, channels * bytePerSample, true);
	/* bits per sample */
	view.setUint16(34, bitsPerSample, true);
	/* data chunk identifier */
	writeString(view, 36, 'data');
	/* data chunk length */
	view.setUint32(40, length, true);

	writeData(view, 44, samples)

	return view;
}


export function writeString(view: DataView, offset: number, str: string) {
	for (var i = 0; i < str.length; i++) {
		view.setUint8(offset + i, str.charCodeAt(i));
	}
}

export function writeData(view: DataView, offset: number, input: Uint8Array){
	for (var i = 0; i < input.length; ++i, ++offset){
		view.setUint8(offset, input[i]);
	}
}

/**
 * creates blob element PCM audio data incl. WAV header
 *
 * @param recBuffers
 * 			the array of buffered audio data, where each entry contains an array for the channels, i.e.
 * 			recBuffers[0]: [channel_1_data, channel_2_data, ..., channel_n_data]
 * 			recBuffers[1]: [channel_1_data, channel_2_data, ..., channel_n_data]
 * 			...
 * 			recBuffers[length-1]: [channel_1_data, channel_2_data, ..., channel_n_data]
 *
 * @returns blob with MIME type audio/wav
 */
export function exportWavFile(recBuffers: Uint8Array[][], sampleRate: number, channels: number, bitsPerSample: number): Blob {

	//convert buffers into one single buffer
	const samples = interleave(recBuffers, channels, bitsPerSample);
	const dataView = encodeWAV(samples, sampleRate, channels, bitsPerSample);
	return new Blob([dataView], {type: 'audio/wav'});
}
